package gen

import (
	"fmt"
	"github.com/iancoleman/strcase"
	"gqlgen-generator/config"
	"gqlgen-generator/gen/graphql"
	"gqlgen-generator/gen/graphql/scalars"
	"gqlgen-generator/gen/graphql/types"
	"gqlgen-generator/gen/table"
	"gqlgen-generator/gen/template"
	"gqlgen-generator/gen/tools"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

type ColumnType uint

// 生成服务
type GenService struct {
	*GenerateContext
}

func (t *GenService) SetConf(gConf config.GenerateConf) {
	t.newContext()
	t.GenerateContext.GenerateConf = &gConf
}

func (t *GenService) newContext() {
	if t.GenerateContext == nil {
		t.GenerateContext = &GenerateContext{}
	}
}

func (t *GenService) Generate() {
	modulePath := filepath.Join(t.ProjectDir, t.ModuleDir)
	if t.GenerateConf.RemoveExistDir {
		err := os.RemoveAll(modulePath)
		if err != nil {
			log.Fatal(err)
		}
	} else {
		exist, err := PathExists(modulePath)
		if err != nil {
			log.Fatal(err)
		}
		if exist {
			log.Fatal("文件夹不为空，不能执行generate")
		}
	}

	// 解析表信息
	tableInfos, err := table.ResolverTableInfos(t.Tables)
	if err != nil {
		log.Fatal(err)
	}
	err = t.ensureAllFile()
	if err != nil {
		log.Fatal(err)
	}

	// 生成gqlgen.yml
	err = t.generateGqlgenYmlFile()
	if err != nil {
		log.Fatal(err)
	}
	// 生成只包含model的graphql文件
	for _, tableInfo := range tableInfos {
		err = t.generateModelFile(tableInfo)
		if err != nil {
			log.Fatal(err)
		}
	}
	err = t.ensureAllFile()
	if err != nil {
		log.Fatal(err)
	}
	// 生成graphql文件
	for _, tableInfo := range tableInfos {
		err = t.generateGraphqlFile(tableInfo, t.TableRelations)
		if err != nil {
			log.Fatal(err)
		}
	}
	err = t.generateOtherGraphqlFile()
	if err != nil {
		log.Fatal(err)
	}
	// 执行gqlgen generate
	_, err = tools.RunInDir("go run github.com/99designs/gqlgen generate.", filepath.Join(t.ProjectDir, t.ModuleDir))
	if err != nil {
		log.Fatal(err)
	}
	// 覆盖resolver函数
	err = t.regenerateResolverFiles(tableInfos)
	if err != nil {
		log.Fatal(err)
	}
	// 生成endpoint.go文件
	err = t.generateEndpointFile()
	if err != nil {
		log.Fatal(err)
	}
}

func (t *GenService) generateGqlgenYmlFile() error {
	templateContext := template.TemplateContext{
		Conf: t.GenerateConf,
	}
	gqlgenYmlPath := filepath.Join(t.ProjectDir, t.ModuleDir, "gqlgen.yml")
	return tools.WriteTemplate(template.GQLGEN_YML, gqlgenYmlPath, templateContext)
}

//生成所有文件夹
func (t *GenService) ensureAllFile() error {
	// 生成工程目录
	err := ensureFile(t.ProjectDir)
	if err != nil {
		return err
	}
	// 生成模块目录
	err = ensureFile(filepath.Join(t.ProjectDir, t.ModuleDir))
	if err != nil {
		return err
	}
	// 生成graphql schema文件目录
	err = ensureFile(filepath.Join(t.ProjectDir, t.ModuleDir, "graph/graphqls"))
	if err != nil {
		return err
	}
	// 生成model目录
	err = ensureFile(filepath.Join(t.ProjectDir, t.ModuleDir, "model"))
	if err != nil {
		return err
	}
	return nil
}

func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// 生成文件夹
func ensureFile(filePath string) error {
	return os.MkdirAll(filePath, 0777)
}

// 生成其他公用的graphql文件
func (t *GenService) generateOtherGraphqlFile() error {
	graphqlPath := filepath.Join(t.ProjectDir, t.ModuleDir, "graph/graphqls", "common.graphqls")
	return ioutil.WriteFile(graphqlPath, []byte(template.COMMON_GRAPHQLS), 0777)
}

// 生成graphql文件
func (t *GenService) generateGraphqlFile(info table.TableInfo, relations []config.TableRelationConf) error {
	graphqlText := graphql.GenerateGraphqlText(&info, relations)
	graphqlPath := filepath.Join(t.ProjectDir, t.ModuleDir, "graph/graphqls", info.TableName+".graphqls")
	return ioutil.WriteFile(graphqlPath, []byte(graphqlText), 0777)
}

// 生成用于生成model的graphql文件
func (t *GenService) generateModelGraphqlFile(info table.TableInfo) error {
	graphqlText := graphql.GenerateModelGraphqlText(&info)
	graphqlPath := filepath.Join(t.ProjectDir, t.ModuleDir, "graph/graphqls", info.TableName+"_model.graphqls")
	return ioutil.WriteFile(graphqlPath, []byte(graphqlText), 0777)
}

// 重新生成resolver文件
func (t *GenService) regenerateResolverFiles(infos []table.TableInfo) error {
	for i, info := range infos {
		// 是否声明resolver struct
		declareResolver := false
		if i == 0 {
			declareResolver = true
		}
		// 联合主键列名
		var unionPrimaryColumnName string
		// 联合主键golang对象名称
		var unionPrimaryName string
		if info.UnionPrimaryKeyColumn() != nil {
			unionPrimaryColumnName = info.UnionPrimaryKeyColumn().Columnname
			if strings.HasSuffix(unionPrimaryColumnName, "_id") {
				bs := []byte(strcase.ToLowerCamel(unionPrimaryColumnName))
				unionPrimaryName = string(bs[:len(bs)-2]) + "ID"
			}
		}
		// 关联查询代码
		relationCode, err := t.generateRelationCode(info)
		if err != nil {
			return err
		}
		context := template.TemplateContext{
			Conf:         t.GenerateConf,
			CurrentTable: info,
			Params: map[string]interface{}{
				// model的golang结构名称
				"ModelName": types.TableModelName(info.TableName),
				// 主键的golang类型
				"PrimaryKeyType": calculateGolangTypeFromGqlType(scalars.GetPrimaryKeyType(info)),
				// 主键的数据库名称
				"PrimaryKeyColumnName": info.PrimaryKeyColumn().Columnname,
				// 主键的golang对象名称
				"PrimaryKeyName": strcase.ToLowerCamel(info.PrimaryKeyColumn().Columnname),
				// 联合主键
				"UnionPrimaryColumnName": unionPrimaryColumnName,
				"UnionPrimaryName":       unionPrimaryName,
				"PrimaryType":            calculateGolangTypeFromGqlType(scalars.GetPrimaryKeyType(info)),
				// 是否声明resolver struct
				"DeclareResolver": declareResolver,
				// 关联查询代码
				"RelationCode": relationCode,
			},
		}
		resolverFilePath := filepath.Join(t.ProjectDir, t.ModuleDir, "graph/resolver", info.TableName+".resolvers.go")
		err = tools.WriteTemplate(template.TABLE_RESOLVER_GO, resolverFilePath, context)
		if err != nil {
			return err
		}
	}
	return nil
}

// 生成关联查询的代码
func (t *GenService) generateRelationCode(info table.TableInfo) (string, error) {

	if len(t.TableRelations) == 0 {
		return "", nil
	}
	relations := make([]config.TableRelationConf, 0)
	for _, relation := range t.TableRelations {
		if relation.Table == info.TableName {
			relations = append(relations, relation)
		}
	}
	if len(relations) == 0 {
		return "", nil
	}
	relationMaps := make([]map[string]interface{}, 0)
	for _, relation := range relations {
		dataloaderColumn := relation.Column
		if strings.HasSuffix(dataloaderColumn, "_id") {
			bs := []byte(strcase.ToCamel(dataloaderColumn))
			dataloaderColumn = string(bs[:len(bs)-2]) + "ID"
		}
		relationMaps = append(relationMaps, map[string]interface{}{
			"FunctionName":     strcase.ToCamel(strings.ReplaceAll(relation.Column, "_id", "")),
			"ParamTypeName":    types.TableModelName(relation.Table),
			"ReturnTypeName":   types.TableModelName(relation.ToTable),
			"DataloaderName":   strcase.ToCamel(relation.ToTable + "_union_pk_loader"),
			"DataloaderColumn": dataloaderColumn,
			"Column":           info.FindColumn(relation.Column),
		})
	}

	context := template.TemplateContext{
		Conf:         t.GenerateConf,
		CurrentTable: info,
		Params: map[string]interface{}{
			"ResolverImplTypeName":  strcase.ToLowerCamel(info.TableName + "_resolver"),
			"ResolverImplFuncName":  strcase.ToCamel(info.TableName),
			"ResolverInterfaceName": strcase.ToCamel(info.TableName + "_resolver"),
			"RelationMaps":          relationMaps,
		},
	}
	bs, err := tools.DoTemplate(template.TABLE_RELATION_RESOLVER_GO, "test", context)
	return string(bs), err
}

// 生成endpoint文件
func (t *GenService) generateEndpointFile() error {
	context := template.TemplateContext{
		Conf: t.GenerateConf,
	}
	resolverFilePath := filepath.Join(t.ProjectDir, t.ModuleDir, "endpoint.go")
	err := tools.WriteTemplate(template.ENDPOINT_GO, resolverFilePath, context)
	return err
}

// 生成model文件
func (t *GenService) generateModelFile(tableInfo table.TableInfo) error {
	err := t.ensureAllFile()
	if err != nil {
		return err
	}
	err = t.generateModelGraphqlFile(tableInfo)
	if err != nil {
		return err
	}
	err = t.generateOtherGraphqlFile()
	if err != nil {
		return err
	}
	// 生成空的model文件
	err = t.generateEmptyModelFile()
	if err != nil {
		return err
	}
	// 执行gqlgen generate 生成model
	_, err = tools.RunInDir("go run github.com/99designs/gqlgen generate.", filepath.Join(t.ProjectDir, t.ModuleDir))
	if err != nil {
		return err
	}
	// 移动model文件
	sourceModelFilePath := filepath.Join(t.ProjectDir, t.ModuleDir, "graph/model/models_gen.go")
	targetModelFilePath := filepath.Join(t.ProjectDir, t.ModuleDir, "model/"+tableInfo.TableName+".go")
	err = os.Rename(sourceModelFilePath, targetModelFilePath)
	if err != nil {
		return err
	}
	// 生成dataloader文件
	//   生成主键dataloader
	tableModuleName := types.TableModelName(tableInfo.TableName)
	generateDataLoaderFileCmd := fmt.Sprintf("go run github.com/vektah/dataloaden %sPkLoader string *%s/%s/model.%s", tableModuleName, t.ProjectName, t.ModuleDir, tableModuleName)
	_, err = tools.RunInDir(generateDataLoaderFileCmd, filepath.Join(t.ProjectDir, t.ModuleDir, "model"))
	if err != nil {
		return err
	}
	//    如果含有联合主键，生成联合主键dataloader
	if tableInfo.UnionPrimaryKeyColumn() != nil {
		generateDataLoaderFileCmd = fmt.Sprintf("go run github.com/vektah/dataloaden %sUnionPkLoader string *%s/%s/model.%s", tableModuleName, t.ProjectName, t.ModuleDir, tableModuleName)
		_, err = tools.RunInDir(generateDataLoaderFileCmd, filepath.Join(t.ProjectDir, t.ModuleDir, "model"))
		if err != nil {
			return err
		}
	}

	// 生成model ext文件
	modelExtFilePath := filepath.Join(t.ProjectDir, t.ModuleDir, "model/"+tableInfo.TableName+"_ext.go")
	context := template.TemplateContext{
		Conf:         t.GenerateConf,
		CurrentTable: tableInfo,
		Params: map[string]interface{}{
			"DataloaderCmd":          generateDataLoaderFileCmd,
			"ModelName":              tableModuleName,
			"PrimaryName":            tableInfo.PrimaryName(),
			"PrimaryType":            calculateGolangTypeFromGqlType(scalars.GetPrimaryKeyType(tableInfo)),
			"PrimaryColumnName":      tableInfo.PrimaryKeyColumn(),
			"UnionPrimaryColumnName": tableInfo.UnionPrimaryKeyColumn(),
			"UnionPrimaryName":       tableInfo.UnionPrimaryName(),
		},
	}
	err = tools.WriteTemplate(template.MODEL_EXT_GO, modelExtFilePath, context)
	if err != nil {
		return err
	}
	// 删除生成的文件
	err = os.RemoveAll(filepath.Join(t.ProjectDir, t.ModuleDir, "graph"))
	if err != nil {
		return err
	}
	return nil
}

func (t *GenService) generateEmptyModelFile() error {
	graphqlPath := filepath.Join(t.ProjectDir, t.ModuleDir, "model", "model.go")
	return ioutil.WriteFile(graphqlPath, []byte(template.EMPTY_MODEL_GO), 0777)
}

// 翻译gqlType到golangType
func calculateGolangTypeFromGqlType(gqlType string) string {
	switch gqlType {
	case scalars.SCALAR_INT:
		return "int"
	case scalars.SCALAR_BIGINT:
		return "int64"

	}
	return "string"
}

// 追加内容到文件
func appendFile(filePath, content string) error {
	fd, _ := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777)
	buf := []byte(content)
	_, err := fd.Write(buf)
	if err != nil {
		return err
	}
	err = fd.Close()
	return err
}
